8장. 가까운 연결, 먼 연결
출처: 『소프트웨어 설계의 결합 균형』(블라드 코노노프 지음, 제이펍 2026) | 원서: Balancing Coupling in Software Design (Manning) · 입문판·PDF 재구성
코드는 분위기만 —
class·def·microservice같은 말은 몰라도 됩니다. 표의 '비유'와 '위험'만 봐도 충분해요.
지난 장들은 두 부품이 얼마나 많이 아는지(연결의 세기)를 봤다.
이번 장은 다른 걸 본다.
같은 양을 알더라도, 그 둘이 얼마나 멀리 떨어져 있는가다.
옆자리 동료에게 한 말은 바로 주워 담을 수 있다.
외국에 있는 사람에게 한 말은 주워 담기 어렵다.
코드도 똑같다.
0. 이 장의 새 단어 (3개)
0장에 없던 말은 딱 3개다.
나머지 어려운 말(결합·공유 지식·공유 수명주기·캡슐화 경계·상위/하위·변경 전파·모놀리식 vs 서비스 분리)은 전부 0장에 있다.
막히면 0장으로 돌아가면 된다.
거리 (Distance)
한 문장 뜻 — 연결된 두 부품이 얼마나 멀리 떨어져 있는가. 같은 메서드 안인지, 다른 서비스인지, 다른 회사인지.
일상비유 — 같은 비밀을 누구와 나누는가. 옆자리 동료면 돌아서서 "없던 걸로" 하면 끝이고, 외국 펜팔이면 시차·언어 때문에 몇 주가 걸린다.
한 줄 예 —
customer.IsPreferred(); // 같은 객체 안 = 거리 가까움
other_service.IsPreferred(); // 다른 서비스 = 거리 멈
부수적 변경 (Collateral Change)
한 문장 뜻 — 원래는 안 건드려도 될 코드인데, 같은 자리에 묶여 있다는 이유만으로 덩달아 함께 바꿔야 하는 변경.
일상비유 — 옆방을 칠하려고 페인트를 사 왔는데, 두 방이 한 통풍구로 이어져 있어 내 방까지 같이 비워야 하는 상황. 내 방은 멀쩡한데 말이다.
한 줄 예 —
// 결제 코드만 고치고 싶은데, 같은 파일의 미완성 알림 코드까지
// 같이 배포되거나 롤백돼야 함 = 부수적 변경
deploy(support_case_file); // Payment 와 Notification 이 한 파일
런타임 결합 (Runtime Coupling)
한 문장 뜻 — 한 부품이 살아 있어야 다른 부품도 일할 수 있는 정도. 상대가 꺼지면 나도 멈추면 결합이 강한 것.
일상비유 — 전화 통화 vs 메모 남기기. 통화는 상대가 받아야 성립하고(상대가 자리 비우면 나도 멈춤), 메모는 책상에 두면 상대가 나중에 읽으면 된다.
한 줄 예 —
var price = call_now(service_a); // service_a 꺼지면 나도 멈춤 (강함)
queue.Send("price_requested"); // 큐에 넣어두면 나는 계속 일함 (약함)
(귀납 도입) 이런 적 있죠?
같은 규칙이 두 군데에 똑같이 적혀 있다고 하자.
"1000원 넘게 쓴 손님은 선호 고객"이라는 규칙이다.
이 규칙을 바꿔야 할 때, 어떤 날은 1분이면 끝나고 어떤 날은 며칠이 걸린다.
왜 이렇게 다를까?
규칙은 똑같은데 말이다.
차이는 그 두 군데가 얼마나 떨어져 있는가다.
아래 두 장면을 보자.
// 장면 1: 같은 객체 안 두 메서드에 같은 규칙
public class CustomerService // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public bool IsPreferredForOrder(Customer c) // 주문 쪽 판단입니다.
{
return c.SpentAmount > 1000; // 같은 기준이 이 객체 안에 있습니다.
}
public bool IsPreferredForCoupon(Customer c) // 쿠폰 쪽 판단입니다.
{
return c.SpentAmount > 1000; // 바꾸려면 같은 클래스 안 두 줄만 보면 됩니다.
}
}
규칙을 바꾸려면? 이 한 파일만 고치면 끝이다.
// 장면 2: 다른 두 서비스에 같은 규칙 (별도 코드베이스)
// 서비스 A
static dynamic IsPreferred(dynamic c) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
return c.SpentAmount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}
// 서비스 B (멀리 떨어진 다른 서비스)
static dynamic IsPreferred(dynamic c) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
return c.SpentAmount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}
규칙을 바꾸려면? 두 서비스를 동시에 고치고 동시에 배포해야 한다.
한쪽만 배포하면 두 서비스의 판단이 어긋나 시스템이 엉킨다.
같은 지식인데 비용이 하늘과 땅이다.
이 차이를 만드는 것이 바로 거리다.
이 장에서 딱 4가지만
이 장에서 딱 4가지만
- 거리 — 같은 지식이라도 먼 데서 공유될수록 고치기 비싸다.
- 두 가지 값 — 거리가 멀면 ① 변경 비용이 커지고 ② 수명주기 결합은 줄어든다(서로 따로 배포 가능).
- 거리는 코드 위치만이 아니다 — 누가 소유하느냐(같은 팀? 다른 회사?)와 어떻게 통신하느냐(통화? 메모?)도 거리를 바꾼다.
- 거리만 늘리면 함정 — 멀리 떼어 놓기만 하고 공유 지식은 그대로면 "분산된 진흙덩이"가 된다. 거리와 지식, 둘 다 봐야 한다.
기본 감각: 같이 변할 운명인 부품은 가까이, 따로 변할 부품은 멀리.
개념 1. 거리 — 같은 지식, 다른 비용
망가지는 장면
선호 고객 기준을 1000원에서 2000원으로 바꾸기로 했다.
같은 규칙이 주문 서비스와 마케팅 서비스, 두 곳에 복사돼 있었다.
한 곳만 고치고 배포했더니, 두 서비스가 서로 다른 손님을 "선호 고객"으로 판단했다.
장바구니에선 할인되는데 결제창에선 할인이 안 되는, 들쭉날쭉한 시스템이 됐다.
일상비유
같은 비밀을 누구와 나누느냐로 비용이 달라진다.
- 옆자리 동료 — 돌아서서 "없던 걸로 하자" 한마디. 비용 거의 0.
- 다른 층 팀원 — 이메일 한 통, 짧은 미팅. 소폭.
- 다른 회사 파트너 — 공식 채널, 일정 조율. 큼.
- 외국 펜팔 — 시차·언어. 몇 주.
소프트웨어도 똑같다.
같은 지식이라도 그 지식이 공유되는 거리에 따라 고치는 비용이 완전히 달라진다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 옆자리 동료(가까움) | 같은 객체 두 메서드 | 한 파일만 고치면 끝 |
| 외국 펜팔(멈) | 두 서비스에 같은 로직 | 동시 수정·동시 배포 강제 |
정의 — 거리는 연결된 두 부품 사이의 공간적 간격이다.
같은 지식을 공유해도 거리가 멀면 변경 비용이 커진다.
예시 1 (worked — 완성예): 가까운 거리
// 같은 객체 안 — 규칙이 한 곳에 있는 셈
public class CustomerService // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public bool IsPreferredForOrder(Customer c) // 주문 흐름에서 쓰는 판단입니다.
{
return c.SpentAmount > 1000; // 같은 규칙을 같은 객체 안에서 공유합니다.
}
public bool IsPreferredForCoupon(Customer c) // 쿠폰 흐름에서 쓰는 판단입니다.
{
return c.SpentAmount > 1000; // 거리 가까움: 이 파일 안에서 함께 바뀝니다.
}
}
기준이 바뀌면 이 클래스 하나만 수정하고 배포한다.
비용: 작음.
예시 2 (worked): 먼 거리
// 서비스 A (주문)
static dynamic IsPreferred(dynamic c) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
return c.SpentAmount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}
// 서비스 B (마케팅, 다른 코드베이스)
static dynamic IsPreferred(dynamic c) // 한 줄짜리 규칙도 호출자가 알게 되는 약속입니다.
{
return c.SpentAmount > 1000; // 계산 결과를 호출자에게 돌려줍니다.
}
기준이 바뀌면 두 서비스를 동시에 고치고 동시에 배포해야 한다.
한쪽만 바꾸면 판단이 어긋난다.
비용: 큼.
예시 3 (부분완성 — 빈칸 채우기)
아래 두 호출 중 거리가 가까운 쪽은?
var a = this.CalcTotal(order); // (A) 같은 객체 안 메서드
var b = remote_service.Calc(order); // (B) 다른 서비스 호출
정답은 (A).
같은 객체 안이라 한 곳만 고치면 된다.
(B)는 멀리 있는 서비스라 양쪽을 맞춰야 한다.
예시 4 (독립적용): 직접 판단
같은 할인 공식이 같은 파일 안 두 함수에 복사돼 있다.
거리는 가까운가, 먼가?
가깝다.
한 파일만 열어 두 줄을 같이 고치면 끝이다.
미니 시나리오
"규칙 한 줄 바꾸는데 왜 옆 팀까지 불러야 하지?"
→ 그 규칙이 옆 팀 서비스에도 복사돼 있던 것.
→ 거리가 먼 곳에 같은 지식이 흩어진 전형. 한 곳으로 모으면 거리가 줄어든다.
단순 규칙 — 같이 변할 운명인 지식은 가까이 둔다. 멀수록 고치기 비싸다.
개념 2. 거리의 두 얼굴 — 변경 비용과 수명주기 결합
거리가 멀어지면 두 가지가 동시에 움직인다.
하나는 커지고, 하나는 줄어든다.
2-1. 변경 비용 (거리에 비례)
망가지는 장면
두 서비스를 같이 바꿔야 하는데, 한 서비스는 다른 팀 소관이다.
티켓을 끊고, 미팅을 잡고, 상대 팀 일정을 기다린다.
코드 두 줄 바꾸는 데 이틀이 걸렸다.
일상비유
가까이 있으면 말로 끝나고, 멀리 있으면 서류와 회의가 필요하다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 말로 끝남(가까움) | 한 파일 안 수정 | 비용 거의 0 |
| 서류·회의 필요(멈) | 두 서비스 동시 배포 | 조정·커뮤니케이션·누락 위험↑ |
정의 — 거리가 멀수록, 같이 바꿔야 할 때 드는 조정 노력이 커진다.
여러 파일을 맞추고, 담당자끼리 소통하고, "저쪽도 바꿔야 했는데" 깜빡할 위험이 늘기 때문이다.
예시 1 (worked): 가까우면 한 번에
// 같은 클래스 — 한 커밋, 한 배포
public class Cart // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public void Add() { this.Total += 1; } // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
public void Clear() { this.Total = 0; } // 규칙 바꿔도 이 클래스만
}
예시 2 (worked): 멀면 줄줄이 조정
// 두 서비스 — 두 커밋, 두 배포, 두 팀
// (각 서비스가 같은 '선호 고객' 규칙을 들고 있음)
order_service.IsPreferred(c); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
marketing_service.IsPreferred(c); // 바꾸려면 양쪽 팀이 함께 움직여야
예시 3 (부분완성): 변경 비용이 더 큰 쪽은?
var x = same_file_func(data); // (A) 같은 파일
var y = partner_api.Func(data); // (B) 외부 파트너 API
(B)가 변경 비용이 크다.
외부 파트너라 계약·버전 협의가 따라붙는다.
예시 4 (독립적용)
같은 로직이 다른 회사 두 시스템에 들어 있다.
변경 비용은?
매우 크다.
회사가 다르면 조정이 가장 무겁다.
2-2. 수명주기 결합 (거리에 반비례)
망가지는 장면
지원 사례 관리 코드만 급히 고쳐 배포하려 했다.
그런데 같은 파일에 전혀 관계없는 미완성 코드(거리 변환 함수)가 절반쯤 작업돼 있었다.
그걸 같이 배포할 순 없어서, 멀쩡한 내 작업까지 멈췄다.
이게 부수적 변경이다.
일상비유
수명주기 결합 = 한집에 같이 사느냐다.
같은 파일·같은 객체에 있으면 한집 식구다 — 이사(배포) 한 번에 다 같이 짐을 싼다.
따로 떨어진 서비스에 있으면 독립 가구다 — 내 짐만 싸면 된다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 한집 식구(가까움) | 한 객체에 여러 기능 | 한 기능 고치면 전체 재배포 |
| 독립 가구(멈) | 기능마다 다른 객체 | 따로 배포 가능 |
정의 — 수명주기 결합은 두 부품이 함께 빌드·테스트·배포돼야 하는 정도다.
거리가 가까울수록 강해지고, 멀수록 약해진다.
가까우면 좋은 줄 알았는데, 너무 가까우면 부수적 변경이 생긴다.
예시 1 (worked — 잘못된 예 before): 신(神)의 객체
// 잘못된 예: 관계없는 기능이 한 객체에 다 묶임
public class SupportCase // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public void CreateCase() { /* 구현 생략 */; } // 핵심
public void ResolveCase() { /* 구현 생략 */; } // 핵심
public void SendEmail() { /* 구현 생략 */; } // 알림 (무관)
public void ProcessPayment() { /* 구현 생략 */; } // 결제 (무관)
public void MilesToKm() { /* 구현 생략 */; } // 단위 변환 (완전 무관)
}
결제만 고치고 싶어도, 같은 파일의 미완성 알림 코드를 롤백하거나 브랜치로 빼야 한다.
서로 무관한데 같은 수명주기에 묶여 부수적 변경이 터진다.
예시 2 (worked — 올바른 예 after): 책임별 분리
// 올바른 예: 기능마다 따로 — 수명주기 독립
class SupportCase: /* 구현 생략 */ // 핵심만
class Notification: /* 구현 생략 */ // 알림만
class Payment: /* 구현 생략 */ // 결제만
class UnitConversion: /* 구현 생략 */ // 단위 변환만
이제 결제 배포가 알림 배포와 따로 논다.
무관한 기능끼리 수명주기가 갈라졌다.
예시 3 (부분완성): 부수적 변경이 생기는 쪽은?
// (A) 알림·결제·변환이 한 파일에 다 있음
// (B) 알림·결제·변환이 각각 다른 파일
부수적 변경은 (A)에서 생긴다.
한 파일이라 하나를 배포하면 나머지도 끌려 나간다.
예시 4 (독립적용)
주문 로직과 이메일 발송 로직이 같은 클래스에 있다.
주문만 급히 고치려는데 이메일 쪽이 미완성이다.
무슨 문제가 생기나?
부수적 변경 — 이메일 미완성 코드 때문에 주문 배포가 막힌다.
단순 규칙 — 따로 변할 기능은 따로 떼어 둔다. 거리가 멀면 수명주기가 풀려 따로 배포할 수 있다.
개념 3. 거리 재는 법 — 공통 조상 찾기
망가지는 장면
"이 두 모듈, 가까워 보이는데 사실 얼마나 떨어진 거지?"
눈대중으로는 알 수 없다.
재는 기준이 필요하다.
일상비유
가족 관계를 따질 때 우리는 공통 조상을 찾는다.
형제는 부모가 공통 조상이라 가깝다.
사촌은 조부모가 공통 조상이라 더 멀다.
공통 조상이 멀수록 사이가 멀다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 형제(부모 공유) | 같은 폴더 안 두 파일 | 가까움 — 같이 고치기 쉬움 |
| 먼 친척(조상 멀음) | 맨 위 폴더만 같음 | 멀음 — 조정 비용 큼 |
정의 — 두 부품의 거리는 가장 가까운 공통 조상 모듈로 잰다.
같은 폴더(가까운 조상)를 공유하면 가깝고, 맨 위 폴더(먼 조상)만 같으면 멀다.
예시 1 (worked): 이름표로 거리 재기
이름표가 점(.)으로 이어진 폴더 경로라고 보자.
Shop.Catalog.Product.Price (타입 1)
Shop.Catalog.Product.Discount (타입 2)
Shop.Orders.Checkout.Payment (타입 3)
타입 1·2의 공통 조상: Shop.Catalog.Product — 가깝다.
타입 1·3의 공통 조상: Shop(맨 위) — 멀다.
예시 2 (worked): 같은 결론, 다른 예
Library.Auth.Login (타입 A)
Library.Auth.Logout (타입 B)
Library.Billing.Invoice (타입 C)
A·B의 공통 조상: Library.Auth — 가깝다.
A·C의 공통 조상: Library(맨 위) — 멀다.
예시 3 (부분완성): 더 가까운 쌍은?
App.User.Profile (1)
App.User.Settings (2)
App.Report.Export (3)
가장 가까운 쌍은? (1)과 (2).
공통 조상이 App.User로 깊다.
(1)·(3)은 App까지 올라가야 만나니 더 멀다.
예시 4 (독립적용)
Mall.Cart.Item과 Mall.Cart.Coupon의 공통 조상은?
Mall.Cart.
같은 Cart 아래라 가깝다.
단순 규칙 — 공통 조상이 깊을수록(아래쪽일수록) 가깝고, 맨 위에서야 만나면 멀다.
개념 4. 거리는 코드 위치만이 아니다 — 소유권과 통신 방식
코드 파일이 어디 있느냐만 거리를 정하는 게 아니다.
두 가지가 더 끼어든다.
4-1. 소유권 거리 — 누가 가졌는가
망가지는 장면
두 서비스가 코드상으론 비슷한 자리에 있다.
그런데 하나는 우리 팀, 하나는 다른 부서 소관이다.
같이 바꾸려니 부서 간 회의부터 잡아야 했다.
코드 거리는 가까운데 체감 거리는 멀었다.
일상비유
이웃과 담장을 같이 고친다고 하자.
- 같은 집 식구 — 저녁 식탁에서 합의, 내일 시작.
- 옆집 이웃 — 약속 잡고 비용 나누기, 며칠.
- 다른 동네 땅주인 — 공식 문서, 몇 주.
- 외국 법인 — 시차·법률, 몇 달.
거리는 물리적이 아니라 누가 소유하느냐로도 늘어난다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 같은 집 식구 | 같은 팀이 두 모듈 소유 | 조정 쉬움 |
| 외국 법인 | 다른 회사가 소유 | 조정 매우 무거움 |
정의 — 소유권 거리는 같은 사람·팀·부서·조직 중 누가 부품을 소유하는가로 생기는 거리다.
소유가 멀어질수록(다른 팀 → 다른 회사) 같이 바꾸는 조정 비용이 커진다.
콘웨이의 법칙 (1968) — "시스템을 설계하는 조직은, 그 조직의 소통 구조를 그대로 닮은 시스템을 만든다."
쉽게: 팀이 셋으로 갈리면, 시간이 지나며 시스템도 그 셋을 닮아 셋으로 갈라진다. 팀이 잘 나뉘어 소통이 깔끔하면 시스템 경계도 깔끔해진다. 팀 사이 소통이 엉키면 시스템도 엉킨다.
예시 1 (worked): 같은 팀이면 가볍다
// 두 서비스, 같은 팀 소유 — 슬랙 한 줄로 조정
order_service.IsPreferred(c); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
billing_service.IsPreferred(c); // "둘 다 2000원으로 바꿀게요" 끝
예시 2 (worked): 다른 회사면 무겁다
// 한쪽이 외부 파트너 API — 버전 협의·계약 필요
our_service.IsPreferred(c); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
partner_api.IsPreferred(c); // 상대 회사 일정에 맞춰야
예시 3 (부분완성): 체감 거리가 더 먼 쪽은?
같은 코드 거리인데:
(A) 두 모듈 모두 우리 팀 소유
(B) 한 모듈은 다른 부서 소유
체감 거리가 먼 쪽은 (B).
소유가 갈리면 조정 비용이 붙는다.
예시 4 (독립적용)
코드는 옆 폴더라 가까운데, 한쪽이 퇴사한 사람만 알던 모듈이라 아무도 손을 못 댄다.
소유권 거리는?
매우 멀다 — 소유자가 사실상 없으니 변경이 가장 위험하다.
4-2. 런타임 결합 — 상대가 살아 있어야 하나
망가지는 장면
결제 서비스가 잠깐 점검으로 꺼졌다.
그랬더니 주문 서비스까지 같이 멈춰 버렸다.
주문은 결제가 지금 당장 응답해 주길 기다리고 있었던 것이다.
일상비유
전화 통화 vs 메모 남기기.
- 전화(동기) — 상대가 받아야 성립. 상대가 자리 비우면 나도 멈춤.
- 메모(비동기) — 책상에 두면 끝. 상대가 나중에 읽어도 됨. 나는 계속 일함.
| 비유 | 코드 | 위험 |
|---|---|---|
| 전화 통화(동기) | call_now(service_a) |
상대 꺼지면 나도 멈춤 |
| 메모 남기기(비동기) | queue.send(msg) |
상대 꺼져도 나는 계속 |
정의 — 런타임 결합은 한 부품이 살아 있어야 다른 부품이 일할 수 있는 정도다.
동기 통신(통화)은 결합이 강하고, 비동기 통신(메모)은 약하다.
런타임 결합이 강하면 한쪽 장애가 다른 쪽으로 번져, 둘의 수명주기가 묶이고 거리가 줄어든다.
예시 1 (worked): 동기 — 강한 런타임 결합
// 주문이 결제의 즉답을 기다림
var result = payment_service.ChargeNow(order); // 결제 꺼지면 주문도 멈춤
예시 2 (worked): 비동기 — 약한 런타임 결합
// 주문은 큐에 던지고 자기 일 계속
queue.Send("order_placed", order); // 결제가 꺼져 있어도 주문은 진행
예시 3 (부분완성): 런타임 결합이 약한 쪽은?
var a = remote.GetNow(id); // (A) 지금 응답 기다림
var b = bus.Publish("evt", data); // (B) 버스에 던지고 끝
(B)가 약하다.
상대가 잠시 꺼져도 나는 멈추지 않는다.
예시 4 (독립적용)
알림 서비스가 메시지 버스를 구독해 천천히 처리한다.
발송 서버가 잠깐 꺼져도 알림은 버스에 쌓여 있다 나중에 처리된다.
런타임 결합은?
약하다 — 비동기라 한쪽 장애가 바로 번지지 않는다.
4-3. 비동기의 함정 — 멀어졌다고 가벼운 건 아니다
비동기(메모)는 런타임 결합과 수명주기 결합을 줄여 준다.
하지만 공유 지식까지 줄여 주진 않는다.
메모에 내 속사정을 그대로 적어 보내면, 속사정을 바꿀 때 상대도 같이 바꿔야 한다.
예시 (잘못된 예 before): 속사정을 그대로 노출
// 비동기지만 내부 필드를 통째로 흘림
queue.Send({ // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
"customer_id": 42,; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
"internal_credit_score": 750,; // 내 속사정
"legacy_account_code": "P"; // 내 속사정
}); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
내부 필드 이름을 바꾸면 받는 쪽도 다 같이 깨진다.
예시 (올바른 예 after): 의도만 노출
// 바깥이 알아야 할 결론만
queue.Send({"customer_id": 42, "is_preferred": true}); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
받는 쪽은 "선호 고객 여부"라는 결론만 본다.
내 속사정이 바뀌어도 상대는 멀쩡하다.
단순 규칙 — 거리(위치·소유·통신)를 봐도 지식 공유량은 따로 챙겨야 한다. 메모에도 속사정을 흘리면 결국 묶인다.
개념 5. 거리와 지식, 둘 다 봐야 한다
망가지는 장면
"마이크로서비스로 쪼개면 느슨해진다며?"
그래서 한 덩어리를 잘게 쪼개 멀리 떼어 놨다.
그런데 쪼갠 서비스들이 여전히 같은 DB 컬럼·같은 규칙을 공유하고 있었다.
거리는 멀어졌는데 지식은 그대로라, 컬럼 하나 바꿀 때마다 다섯 서비스가 줄줄이 깨졌다.
이게 "분산된 진흙덩이"다.
일상비유
이사로 거리를 늘렸다고 부부가 남이 되는 건 아니다.
각자 다른 도시로 이사 가도(거리↑) 통장을 같이 쓰면(지식 공유 그대로) 한쪽 지출이 다른 쪽에 그대로 영향을 준다.
멀어지기만 하고 함께 아는 것을 안 줄이면, 오히려 멀어서 더 불편해진다.
| 비유 | 코드 | 위험 |
|---|---|---|
| 멀리 이사+통장 공유 | 서비스 쪼갰는데 DB 공유 | 거리↑·지식 그대로 = 최악 |
| 멀리 이사+살림 분리 | 서비스마다 자기 데이터 | 진짜로 느슨해짐 |
정의 — 거리는 얼마나 멀리서 아는가, 통합 강도(지난 장 주제)는 얼마나 많이 아는가다.
둘은 서로 다른 축이다.
거리만 늘리고 지식 공유를 안 줄이면 "분산된 진흙덩이"가 된다.
예시 1 (worked — 잘못된 예 before): 거리만 늘림
// 결제 서비스가 주문 서비스의 DB 에 직접 쿼리
var rows = order_db.Query("SELECT amount FROM orders"); // 남의 속사정 공유
서비스는 갈렸지만(거리↑) DB 컬럼 지식은 그대로다.
amount 컬럼명을 바꾸면 결제가 깨진다.
예시 2 (worked — 올바른 예 after): 지식도 줄임
// 결제는 공식 API 로 결론만 받음
var total = order_service.GetTotal(order_id); // 컬럼이 아니라 결과만
주문 서비스가 내부 컬럼을 바꿔도 결제는 멀쩡하다.
거리도 멀고, 지식 공유도 적다 — 진짜 느슨함.
예시 3 (부분완성): 진짜 느슨한 쪽은?
(A) 서비스 쪼갬 + 서로의 DB 직접 조회; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
(B) 서비스 쪼갬 + 공식 API 로 결론만 교환; // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
진짜 느슨한 쪽은 (B).
거리뿐 아니라 공유 지식까지 줄였다.
예시 4 (독립적용)
두 서비스를 멀리 떼어 놨는데, 한쪽이 다른 쪽의 내부 메시지 형식을 통째로 안다.
이건 느슨한가?
아니다 — 거리만 멀 뿐 지식은 그대로라 "분산된 진흙덩이"다.
미니 시나리오
"쪼갰는데 왜 더 복잡해졌지?"
→ 거리만 늘리고 공유 지식을 안 줄인 것.
→ 공식 인터페이스로 결론만 주고받게 바꾸면 그제야 느슨해진다.
단순 규칙 — 멀리 떼어 놓을 땐 공유하는 지식도 함께 줄여라. 거리만 늘리면 더 나빠진다.
거리 한눈에 보기
| 경계 | 거리 | 변경 비용 | 수명주기 결합 |
|---|---|---|---|
| 같은 메서드 | 가장 가까움 | 가장 낮음 | 가장 강함 |
| 같은 클래스 | 가까움 | 낮음 | 강함 |
| 같은 폴더(네임스페이스) | 중간 | 중간 | 중간 |
| 다른 라이브러리 | 멈 | 높음 | 약함 |
| 다른 서비스 | 가장 멈 | 가장 높음 | 가장 약함 |
거리에 추가로 영향을 주는 것:
| 요인 | 묻는 것 | 효과 |
|---|---|---|
| 코드 위치 | 공통 조상이 얼마나 깊나 | 거리의 1차 기준 |
| 소유권 | 같은 팀인가, 다른 회사인가 | 멀수록 조정 비용↑ |
| 통신 방식 | 통화(동기)인가 메모(비동기)인가 | 동기면 장애 전파로 묶임 |
정리
- 거리는 같은 지식이라도 얼마나 멀리서 공유되는가다. 멀수록 고치기 비싸다.
- 거리가 멀면 변경 비용↑, 수명주기 결합↓ (따로 배포 가능). 같이 변할 건 가까이, 따로 변할 건 멀리.
- 거리는 코드 위치만이 아니라 소유권(누가 가졌나)·통신 방식(통화냐 메모냐)으로도 늘어난다.
- 멀리 떼어 놓기만 하고 공유 지식을 안 줄이면 "분산된 진흙덩이"가 된다. 거리와 지식, 둘 다 본다.
한 걸음 더 ▸ (지금 몰라도 됨)
"거리는 무조건 멀수록 좋다"가 아니다. 멀면 변경 비용이 커지고, 가까우면 수명주기가 묶인다 — 양쪽 다 비용이 있다. 그래서 어디까지 멀게 하고 어디까지 가깝게 둘지를 지식 공유량과 함께 저울질하는 게 진짜 균형이다. 이건 더 깊은 주제라, 지금은 "같이 변할 건 가까이, 따로 변할 건 멀리" 감각 하나만 들고 가면 된다.
더 해보기
- 마이크로서비스로 쪼갤 때 "거리만 늘린 진흙덩이"를 피하는 패턴 모음: microservices.io 패턴 카탈로그 (검증 2026-05-21).
- 콘웨이의 법칙을 역이용해 원하는 시스템에 맞게 팀부터 짜는 접근(역콘웨이): Team Topologies 공식 (검증 2026-05-21).
다음 9장 예고 — 또 다른 결합의 축, 시간에 따라 얼마나 자주 변하는가(변동성)를 본다. (지금 몰라도 됩니다 — 9장에서 천천히 풀려요.)
연습문제
- 설명.
가까운 연결 먼 연결의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라. - 구분. 두 개념(
가까운 연결,먼 연결)을 실제 예시 하나로 구분하라. - 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.
부록 A. 쉬운 용어 사전
| 한글 용어 | 원문 영문명 | 아주 쉬운 뜻 | 이 장에서 나온 위치 |
|---|---|---|---|
| 가까운 연결 | - | 함께 고치고 배포하기 쉬운 연결. | 부록 B와 본문 예시 |
| 먼 연결 | - | 사람, 팀, 코드 위치, 배포 단위가 떨어져 조율 비용이 큰 연결. | 부록 B와 본문 예시 |
| 코드 거리 | Code Distance | 파일, 모듈, 저장소 같은 코드 위치상의 거리. | 부록 B와 본문 예시 |
| 소유권 거리 | Ownership Distance | 담당자나 팀이 달라서 생기는 협업상의 거리. | 부록 B와 본문 예시 |
부록 B. 헷갈리는 개념 비교표
| A | B | 구분 포인트 |
|---|---|---|
| 가까운 연결 | 먼 연결 | 가까우면 함께 바꾸기 쉽고, 멀면 조율 비용이 커진다. |
| 코드 거리 | 소유권 거리 | 코드 거리는 파일/모듈 위치, 소유권 거리는 사람과 팀의 거리다. |
부록 C. 더 읽을 자료
- 이 장의
더 해보기섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다. - 같은 책의
0장 한눈에 보기— 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다. - 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
- 이 장의
flashcards.json— 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.
부록 D. 연습문제 풀이
- 설명 예시.
가까운 연결 먼 연결는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다. - 구분 예시. 두 개념(
가까운 연결,먼 연결)의 차이는 이렇게 잡으면 된다. 가까우면 함께 바꾸기 쉽고, 멀면 조율 비용이 커진다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다. - 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
클릭하거나 Space를 눌러 뒤집기